﻿using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;

namespace Devonline.AspNetCore;

/// <summary>
/// 数据库上下文扩展操作集合
/// </summary>
public static class DbContextExtensions
{
    /// <summary>
    /// 自动迁移
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <returns></returns>
    public static async Task AutoMigrationAsync<TDbContext>(this TDbContext context) where TDbContext : DbContext
    {
        if ((await context.Database.GetPendingMigrationsAsync()).Any())
        {
            await context.Database.MigrateAsync();
        }
    }

    /// <summary>
    /// 清空目标数据库表数据, 清空方式: 使用 truncate 语句截断表
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntity">数据对象模型类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="databaseType">数据库类型</param>
    /// <returns>受影响的数量</returns>
    public static async Task<int> ClearAsync<TDbContext, TEntity>(this TDbContext context, DatabaseType databaseType = DatabaseType.PostgreSQL) where TDbContext : DbContext => await ClearAsync(context, typeof(TEntity), databaseType);
    /// <summary>
    /// 清空目标数据库表数据, 清空方式: 使用 truncate 语句截断表
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="type">数据对象模型</param>
    /// <param name="databaseType">数据库类型</param>
    /// <returns>受影响的数量</returns>
    public static async Task<int> ClearAsync<TDbContext>(this TDbContext context, Type type, DatabaseType databaseType = DatabaseType.PostgreSQL) where TDbContext : DbContext
    {
        var separators = databaseType.GetDatabaseSeparator();
        var separatorLeft = char.MinValue;
        var separatorRight = char.MinValue;
        if (separators.Length >= 1)
        {
            separatorLeft = separators[0];
            separatorRight = separators.Length >= 2 ? separators[1] : separators[0];
        }

        var sql = $"TRUNCATE TABLE {separatorLeft}{type.GetTableName()}{separatorLeft} CASCADE";
        return await context.Database.ExecuteSqlRawAsync(sql);
    }

    /// <summary>
    /// 迁移目标数据库表数据
    /// </summary>
    /// <typeparam name="TSourceDbContext"></typeparam>
    /// <typeparam name="TTargetDbContext"></typeparam>
    /// <typeparam name="TEntity"></typeparam>
    /// <param name="sourceDbContext"></param>
    /// <param name="targetDbContext"></param>
    /// <param name="databaseType">数据库类型</param>
    /// <returns></returns>
    public static async Task MigrationAsync<TSourceDbContext, TTargetDbContext, TEntity>(this TSourceDbContext sourceDbContext, TTargetDbContext targetDbContext, DatabaseType databaseType = DatabaseType.PostgreSQL) where TSourceDbContext : DbContext where TTargetDbContext : DbContext where TEntity : class, IEntitySet
    {
        var data = await sourceDbContext.Set<TEntity>().ToListAsync();
        var total = data.Count;
        if (total != 0)
        {
            var result = await targetDbContext.BulkInsertAsync(data, databaseType, data.Count);
            data.Clear();
        }
    }

    /// <summary>
    /// 使用 insert into table (columns) values (values), (values)... 的语法批量写入数据
    /// 在单次写入数量或字符限制大小某一个达到限制值时即提交一次
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">待写入的数据类型</typeparam>
    /// <typeparam name="TKey">主键类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="data">待写入数据</param>
    /// <param name="limit">单次写入限制数量, 默认 1000, 这个值由数据库链接决定</param>
    /// <param name="separators">字段引用符号, 针对字段名是 数据库关键字的情况下, 每种数据库有不同的引用表示方式, 如 SQLServer: [], MySQL:` , 默认无</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static async Task<int> BulkInsertAsync<TDbContext, TEntitySet, TKey>(this TDbContext context, IEnumerable<TEntitySet> data, int limit = UNIT_THOUSAND, params char[] separators) where TDbContext : DbContext where TEntitySet : class, IEntitySet<TKey>
    {
        if (data == null || !data.Any())
        {
            return 0;
        }

        var type = typeof(TEntitySet);
        var propertyInfos = type.GetProperties().Where(x => x.HasAttribute<ColumnAttribute>() && !x.HasAttribute<NotMappedAttribute>());
        var separatorLeft = char.MinValue;
        var separatorRight = char.MinValue;
        if (separators.Length >= 1)
        {
            separatorLeft = separators[0];
            separatorRight = separators.Length >= 2 ? separators[1] : separators[0];
        }

        //并发构造值的集合
        var total = data.Count();
        var values = new List<string>();
        foreach (var entity in data)
        {
            try
            {
                var sqls = new List<string>();
                foreach (var propertyInfo in propertyInfos)
                {
                    sqls.Add(entity.GetSqlStringValue<TEntitySet>(propertyInfo) ?? "NULL");
                }

                values.Add($"({string.Join(DEFAULT_SPLITER_STRING, sqls)})");
                sqls.Clear();
            }
            catch (Exception ex)
            {
                throw new Exception($"The bulk insert data of {type.Name} error, total: {total}, the entity is: {entity.ToJsonString()}", ex);
            }
        }

        //写入数据库
        if (values.Count <= 0)
        {
            return 0;
        }

        var result = 0;
        var sql = string.Empty;

        try
        {
            var sqlInsert = $"INSERT INTO {separatorLeft}{type.GetTableName()}{separatorRight} ({string.Join(DEFAULT_SPLITER_STRING, propertyInfos.Select(p => separatorLeft + p.GetColumnName() + separatorRight))}) VALUES ";
            for (int index = 0; index < values.Count; index += limit)
            {
                var end = index + limit;
                if (end > values.Count)
                {
                    end = values.Count;
                }

                sql = sqlInsert + Environment.NewLine + string.Join(DEFAULT_SPLITER_STRING + Environment.NewLine, values[index..end]) + CHAR_SEMICOLON;
                result += await context.Database.ExecuteSqlRawAsync(sql);
            }

            values.Clear();
            return result;
        }
        catch (Exception ex)
        {
            throw new Exception($"The last times to bulk insert data of {type.Name} error, total: {total}, current: {result}, the sql is: " + sql, ex);
        }
    }
    /// <summary>
    /// 使用 insert into table (columns) values (values), (values)... 的语法批量写入数据
    /// 字符串作为主键的默认实现
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">待写入的数据类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="data">待写入数据</param>
    /// <param name="limit">单次写入限制数量, 默认 1000, 这个值由数据库链接决定</param>
    /// <param name="separators">字段引用符号, 针对字段名是 数据库关键字的情况下, 每种数据库有不同的引用表示方式, 如 SQLServer: [], MySQL:` , 默认无</param>
    /// <returns></returns>
    public static async Task<int> BulkInsertAsync<TDbContext, TEntitySet>(this TDbContext context, IEnumerable<TEntitySet> data, int limit = UNIT_HUNDRED, params char[] separators) where TDbContext : DbContext where TEntitySet : class, IEntitySet => await context.BulkInsertAsync<TDbContext, TEntitySet, string>(data, limit, separators);
    /// <summary>
    /// 使用 insert into table (columns) values (values), (values)... 的语法批量写入数据
    /// 在单次写入数量或字符限制大小某一个达到限制值时即提交一次
    /// 本方法根据已知数据库类型构建 sql 语句完成批量写入
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">待写入的数据类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="data">待写入数据</param>
    /// <param name="databaseType">数据库类型, 默认使用 PostgreSQL 数据库</param>
    /// <param name="limit">单次写入限制数量, 默认 1000, 这个值由数据库链接决定</param>
    /// <returns></returns>
    public static async Task<int> BulkInsertAsync<TDbContext, TEntitySet>(this TDbContext context, IEnumerable<TEntitySet> data, DatabaseType databaseType = DatabaseType.PostgreSQL, int limit = UNIT_THOUSAND) where TDbContext : DbContext where TEntitySet : class, IEntitySet => await context.BulkInsertAsync(data, limit, databaseType.GetDatabaseSeparator());

    /// <summary>
    /// 使用 update table set column1=value1,column2=value ... 的语法批量更新数据
    /// 在单次更新数量或字符限制大小某一个达到限制值时即提交一次
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">待更新的数据类型</typeparam>
    /// <typeparam name="TKey">主键类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="data">待更新数据</param>
    /// <param name="limit">单次更新限制数量, 默认 1000, 这个值由数据库链接决定</param>
    /// <param name="separators">字段引用符号, 针对字段名是 数据库关键字的情况下, 每种数据库有不同的引用表示方式, 如 SQLServer: [], MySQL:` , 默认无</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static async Task<int> BulkUpdateAsync<TDbContext, TEntitySet, TKey>(this TDbContext context, IEnumerable<TEntitySet> data, int limit = UNIT_THOUSAND, params char[] separators) where TDbContext : DbContext where TEntitySet : class, IEntitySet<TKey>
    {
        if (data == null || !data.Any())
        {
            return 0;
        }

        var type = typeof(TEntitySet);
        var propertyInfos = type.GetProperties().Where(x => x.HasAttribute<ColumnAttribute>() && !x.HasAttribute<NotMappedAttribute>());
        var separatorLeft = char.MinValue;
        var separatorRight = char.MinValue;
        if (separators.Length >= 1)
        {
            separatorLeft = separators[0];
            separatorRight = separators.Length >= 2 ? separators[1] : separators[0];
        }

        var total = data.Count();
        var values = new List<string>();
        var sqlUpdate = $"UPDATE {separatorLeft}{type.GetTableName()}{separatorRight} SET ";

        //并发构造值的集合
        foreach (var entity in data)
        {
            try
            {
                var sqls = new List<string>();
                var keys = new List<string>();
                foreach (var propertyInfo in propertyInfos)
                {
                    var stringValue = entity.GetSqlStringValue<TEntitySet>(propertyInfo) ?? "NULL";
                    var fieldExpression = separatorLeft + propertyInfo.GetColumnName() + separatorRight + CHAR_EQUAL + stringValue;
                    if (propertyInfo.HasAttribute<KeyAttribute>())
                    {
                        keys.Add(fieldExpression);
                    }
                    else
                    {
                        sqls.Add(fieldExpression);
                    }
                }

                values.Add(sqlUpdate + string.Join(DEFAULT_SPLITER_STRING, sqls) + " WHERE " + string.Join($" and ", keys));
                sqls.Clear();
                keys.Clear();
            }
            catch (Exception ex)
            {
                throw new Exception($"The last times to bulk update data of {type.Name} error, total: {total}, the entity is: {entity.ToJsonString()}", ex);
            }
        }

        //写入数据库
        if (values.Count <= 0)
        {
            return 0;
        }

        var result = 0;
        var sql = string.Empty;

        try
        {
            for (int index = 0; index < values.Count; index += limit)
            {
                var end = index + limit;
                if (end > values.Count)
                {
                    end = values.Count;
                }

                sql = string.Join(CHAR_SEMICOLON + Environment.NewLine, values[index..end]) + CHAR_SEMICOLON;
                total += await context.Database.ExecuteSqlRawAsync(sql);
            }

            values.Clear();
            return total;
        }
        catch (Exception ex)
        {
            throw new Exception($"The last times to bulk update data of {type.Name} error, total: {total}, current: {result}, the sql is: " + sql, ex);
        }
    }
    /// <summary>
    /// 使用 update table set column1=value1,column2=value ... 的语法批量更新数据
    /// 字符串作为主键的默认实现
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">待更新的数据类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="data">待更新数据</param>
    /// <param name="limit">单次更新限制数量, 默认 1000, 这个值由数据库链接决定</param>
    /// <param name="separators">字段引用符号, 针对字段名是 数据库关键字的情况下, 每种数据库有不同的引用表示方式, 如 SQLServer: [], MySQL:` , 默认无</param>
    /// <returns></returns>
    public static async Task<int> BulkUpdateAsync<TDbContext, TEntitySet>(this TDbContext context, IEnumerable<TEntitySet> data, int limit = UNIT_HUNDRED, params char[] separators) where TDbContext : DbContext where TEntitySet : class, IEntitySet => await context.BulkUpdateAsync<TDbContext, TEntitySet, string>(data, limit, separators);
    /// <summary>
    /// 使用 update table set column1=value1,column2=value ... 的语法批量更新数据
    /// 在单次更新数量或字符限制大小某一个达到限制值时即提交一次
    /// 本方法根据已知数据库类型构建 sql 语句完成批量更新
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">待写入的数据类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="data">待写入数据</param>
    /// <param name="databaseType">数据库类型, 默认使用 PostgreSQL 数据库</param>
    /// <param name="limit">单次写入限制数量, 默认 1000, 这个值由数据库链接决定</param>
    /// <returns></returns>
    public static async Task<int> BulkUpdateAsync<TDbContext, TEntitySet>(this TDbContext context, IEnumerable<TEntitySet> data, DatabaseType databaseType = DatabaseType.PostgreSQL, int limit = UNIT_HUNDRED) where TDbContext : DbContext where TEntitySet : class, IEntitySet => await context.BulkUpdateAsync(data, limit, databaseType.GetDatabaseSeparator());

    /// <summary>
    /// 递归获取最顶级父级数据 
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <typeparam name="TKey">主键类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="id">当前业务数据编号</param>
    /// <returns></returns>
    public static async Task<TEntitySet?> GetTopParentAsync<TDbContext, TEntitySet, TKey>(this TDbContext context, TKey id) where TDbContext : DbContext where TKey : IConvertible, IEquatable<TKey> where TEntitySet : class, IParent<TKey>, IEntitySetWithCreate<TKey>
    {
        var entitySet = await context.Set<TEntitySet>().FindAsync(id);
        if (entitySet is not null && entitySet.ParentId is not null && entitySet.State == DataState.Available)
        {
            return await GetTopParentAsync<TDbContext, TEntitySet, TKey>(context, entitySet.ParentId);
        }

        return entitySet;
    }
    /// <summary>
    /// 递归获取父级数据 
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <typeparam name="TKey">主键类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="id">当前业务数据编号</param>
    /// <returns></returns>
    public static async Task<List<TEntitySet>> GetParentsAsync<TDbContext, TEntitySet, TKey>(this TDbContext context, TKey id) where TDbContext : DbContext where TKey : IConvertible, IEquatable<TKey> where TEntitySet : class, IParent<TKey>, IEntitySetWithCreate<TKey>
    {
        var entitySets = new List<TEntitySet>();
        var entitySet = await context.Set<TEntitySet>().FindAsync(id);
        if (entitySet is not null && entitySet.ParentId is not null && entitySet.State == DataState.Available)
        {
            var parent = await context.Set<TEntitySet>().FindAsync(entitySet.ParentId);
            if (parent is not null && entitySet.State == DataState.Available)
            {
                entitySets.Add(parent);
                entitySets.AddRange(await context.GetParentsAsync<TDbContext, TEntitySet, TKey>(parent.Id));
            }
        }

        return entitySets;
    }
    /// <summary>
    /// 获取子级数据 
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <typeparam name="TKey">主键类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="id">当前业务数据编号</param>
    /// <param name="recursion">是否向下递归获取</param>
    /// <returns></returns>
    public static async Task<List<TEntitySet>> GetChildrenAsync<TDbContext, TEntitySet, TKey>(this TDbContext context, TKey id, bool recursion = false) where TDbContext : DbContext where TKey : IConvertible, IEquatable<TKey> where TEntitySet : class, IParent<TKey>, IEntitySetWithCreate<TKey>
    {
        var entitySets = new List<TEntitySet>();
        if (id is not null)
        {
            var children = await context.Set<TEntitySet>().AsNoTracking().Where(x => x.State == DataState.Available && x.ParentId != null && x.ParentId.Equals(id)).ToListAsync();
            if (children.Count != 0)
            {
                entitySets.AddRange(children);
                if (recursion)
                {
                    foreach (var child in children)
                    {
                        entitySets.AddRange(await context.GetChildrenAsync<TDbContext, TEntitySet, TKey>(child.Id));
                    }
                }
            }
        }

        return entitySets;
    }

    /// <summary>
    /// 递归获取最顶级父级数据 
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="id">当前业务数据编号</param>
    /// <returns></returns>
    public static async Task<TEntitySet?> GetTopParentAsync<TDbContext, TEntitySet>(this TDbContext context, string id) where TDbContext : DbContext where TEntitySet : class, IParent, IEntitySetWithCreate => await context.GetTopParentAsync<TDbContext, TEntitySet, string>(id);
    /// <summary>
    /// 递归获取父级数据, 字符串作为主键的默认实现
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="id">当前业务数据编号</param>
    /// <returns></returns>
    public static async Task<List<TEntitySet>> GetParentsAsync<TDbContext, TEntitySet>(this TDbContext context, string id) where TDbContext : DbContext where TEntitySet : class, IParent, IEntitySetWithCreate => await context.GetParentsAsync<TDbContext, TEntitySet, string>(id);
    /// <summary>
    /// 递归获取子级数据, 字符串作为主键的默认实现
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="id">当前业务数据编号</param>
    /// <param name="recursion">是否向下递归获取</param>
    /// <returns></returns>
    public static async Task<List<TEntitySet>> GetChildrenAsync<TDbContext, TEntitySet>(this TDbContext context, string id, bool recursion = false) where TDbContext : DbContext where TEntitySet : class, IParent, IEntitySetWithCreate => await context.GetChildrenAsync<TDbContext, TEntitySet, string>(id, recursion);

    /// <summary>
    /// 从缓存/数据库获取 TEntity 类型的 TResult 类型的属性/字段 memberName 的下一个值
    /// maxLength 用于约束最大长度和截取非数字部分, 如果不传递, 则默认取当前编号的长度
    /// 如: 字段 Code, 构成为: NX+8位数字, 则 maxLength = 8, 会截取后 8 位数字
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">数据对象模型类型</typeparam>
    /// <typeparam name="TResult">字段/属性类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="memberSelecter">选择字段/属性名称</param>
    /// <param name="maxLength">编码纯数字部分最大长度, 非必须, 默认为字段最后一个值的长度</param>
    /// <param name="prefix">非数字部分前缀, 非必须, 默认为空, 或字段最后一个值除去 maxLength 的前半部分</param>
    /// <param name="maxInterval">最大随机跳过编号, 跳过编号, 会使生成的编号不连续, 防止被盲猜. 非必须, 默认为: 1, 生成连续的编号</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static async Task<TResult> GetNextCodeAsync<TDbContext, TEntitySet, TResult>(this TDbContext context, IDistributedCache cache, Expression<Func<TEntitySet, TResult>> memberSelecter, int? maxLength = default, string? prefix = default, int maxInterval = UNIT_ONE) where TDbContext : DbContext where TEntitySet : class
    {
        var code = string.Empty;
        var codeIndex = 0;
        var lockVakue = KeyGenerator.GetKey<string>();
        var typeName = typeof(TEntitySet).Name.ToUpperInvariant();
        var cacheLockKey = CACHE_APPLICATION + typeName + CHAR_UNDERLINE + "NEXT_LOCK";
        var cacheCodeKey = CACHE_APPLICATION + typeName + CHAR_UNDERLINE + "NEXT_CODE";
        var dbset = context.Set<TEntitySet>();
        var memberInfo = (memberSelecter.Body as MemberExpression)!.Member;

        //循环获取下一个编码
        while (codeIndex++ <= UNIT_HUNDRED)
        {
            //先读锁的值, 如果是空, 则未有线程使用, 否则等待 10ms 再次获取, 直到锁被释放时锁的值为空
            var cacheLock = await cache.GetStringAsync(cacheLockKey);
            if (cacheLock is null)
            {
                lock (dbset)
                {
                    //写入锁
                    cache.SetString(cacheLockKey, lockVakue);

                    //延迟1~10ms
                    var random = new Random(DateTime.Now.Nanosecond);
                    Thread.Sleep(random.Next(UNIT_ONE, UNIT_TEN));

                    //再次读
                    cacheLock = cache.GetString(cacheLockKey);

                    //如果相同说明写入成功, 则继续获取, 写入不成功则下一轮继续
                    if (cacheLock == lockVakue)
                    {
                        //优先从缓存获取, 因为有可能其他线程调用了此方法, 缓存中已经存在了最大的值
                        var cacheCode = cache.GetString(cacheCodeKey);
                        if (cacheCode is null)
                        {
                            //未能从缓存获取到就从数据库获取当前字段值最大的一条
                            cacheCode = dbset.OrderByDescending(memberSelecter).Select(memberSelecter).FirstOrDefault()?.ToString();
                            if (string.IsNullOrWhiteSpace(cacheCode))
                            {
                                cacheCode = UNIT_ZERO.ToString();
                            }
                        }

                        //获取到了, 进行校验, 未获取到进行下一轮
                        if (!string.IsNullOrWhiteSpace(cacheCode))
                        {
                            if (maxLength.HasValue)
                            {
                                if (maxLength.Value < cacheCode.Length)
                                {
                                    //maxLength < cacheCode.Length, 则截取前缀和编码纯数字部分
                                    var length = cacheCode.Length - maxLength.Value;
                                    prefix ??= cacheCode[..length];
                                    cacheCode = cacheCode[length..];
                                }
                                else if (maxLength.Value > cacheCode.Length)
                                {
                                    //maxLength > cacheCode.Length, 则补齐纯数字部分
                                    cacheCode = cacheCode.PadLeft(maxLength.Value, CHAR_ZERO);
                                }
                                else
                                {
                                    //maxLength == cacheCode.Length, 则忽略前缀
                                    prefix = null;
                                }
                            }
                            else
                            {
                                maxLength = cacheCode.Length;
                            }

                            try
                            {
                                //编号 +x 后计算下一个编号
                                var add = (maxInterval == UNIT_ONE) ? UNIT_ONE : random.Next(UNIT_ONE, maxInterval);
                                cacheCode = (Convert.ToInt64(cacheCode) + add).ToString().PadLeft(maxLength.Value, CHAR_ZERO);

                                //查询数据库验证重复, 存在重复的就继续下一次获取
                                var anyExpression = memberInfo.GetMemberEqualExpression<TEntitySet, TResult>(cacheCode.To<TResult>()!);
                                if (!dbset.Any(anyExpression))
                                {
                                    //构造完整的编号
                                    if (!string.IsNullOrWhiteSpace(prefix))
                                    {
                                        cacheCode = prefix + cacheCode;
                                    }

                                    //不重复就写入当前最新的编号到缓存
                                    cache.SetString(cacheCodeKey, cacheCode);

                                    //赋值编号
                                    code = cacheCode;
                                }
                            }
                            catch (Exception)
                            {
                                //抛异常时, 不做处理
                            }
                        }

                        //清空锁缓存
                        cache.Remove(cacheLockKey);
                    }
                }
            }

            //执行一轮, 未获取到值就延迟 10ms 继续下一轮获取; 获取到值则退出
            if (string.IsNullOrWhiteSpace(code))
            {
                await Task.Delay(UNIT_TEN);
            }
            else
            {
                break;
            }
        }

        if (string.IsNullOrWhiteSpace(code))
        {
            throw new Exception($"未能成功获取有效编号, 且经过 {UNIT_HUNDRED} 次尝试均未能成功!");
        }

        return code.To<TResult>()!;
    }
    /// <summary>
    /// 从缓存/数据库获取 TEntity 类型的 字符串 类型的属性/字段 memberName 的下一个值
    /// maxLength 用于约束最大长度和截取非数字部分, 如果不传递, 则默认取当前编号的长度
    /// 如: 字段 Code, 构成为: NX+8位数字, 则 maxLength = 8, 会截取后 8 位数字
    /// </summary>
    /// <typeparam name="TDbContext">数据库上下文类型</typeparam>
    /// <typeparam name="TEntitySet">数据对象模型类型</typeparam>
    /// <param name="context">数据库上下文</param>
    /// <param name="cache">缓存</param>
    /// <param name="memberSelecter">选择字段/属性名称</param>
    /// <param name="maxLength">编码纯数字部分最大长度, 非必须, 默认为字段最后一个值的长度</param>
    /// <param name="prefix">非数字部分前缀, 非必须, 默认为空, 或字段最后一个值除去 maxLength 的前半部分</param>
    /// <param name="maxInterval">最大随机跳过编号, 跳过编号, 会使生成的编号不连续, 防止被盲猜. 非必须, 默认为: 1, 生成连续的编号</param>
    /// <returns></returns>
    /// <exception cref="Exception"></exception>
    public static async Task<string> GetNextCodeAsync<TDbContext, TEntitySet>(this TDbContext context, IDistributedCache cache, Expression<Func<TEntitySet, string>> memberSelecter, int? maxLength = default, string? prefix = default, int maxInterval = UNIT_ONE) where TDbContext : DbContext where TEntitySet : class => await context.GetNextCodeAsync<TDbContext, TEntitySet, string>(cache, memberSelecter, maxLength, prefix, maxInterval);
}